#ifndef CELLOBJECT_H
#define CELLOBJECT_H

#if defined(WIN32) || defined(linux)
    #include <QtOpenGL/qgl.h>
#elif defined(__APPLE__)
    #include <OpenGl/glu.h>
#endif

#include <vector>
#include "bpa.h"
#include "common.h"
#include "cellmath.h"
#include "csg.h"

using namespace std;

//!  A Cell Object class.
/*!
  This class restores and displays input basement membrane and user generate objects such as spheres, cubes, cylinders, and planes.
*/


class CellObject
{
public:
    //! A constructor.
    CellObject();

    //! A deconstructor.
    ~CellObject();

    //! A reconstructor.
    CellObject* clone();

    //! Set the name of the object.
    void setName(string n) {name.assign(n.begin(), n.end());}

    //! Return the name of the object.
    string getName() {return name;}

    //! Add a basement membrance.
    /*!
          \param num the number of vertice.
          \param vec a std::vector store all vertice.
          \param nor a std::vector store all normals.
    */
    void CreateCellObject(int num, vector<double> vec, vector<double> nor);

    //! Use vertice array to draw the object.
    /*!
          \param id the name id of this object.
          \param selected the selected object id at the moment.
          \param displayMode can be following values:\n
          CELL_TRIANGLE: display the mesh using filled triangles.\n
          CELL_LINE: display the mesh using lines.\n
          CELL_POINT: display the mesh using points.\n
    */
    void DrawCellObject(int id, int selected, int displayMode);

    //! Directly draw the object.
    /*!
          \param id the name id of this object.
          \param selected the selected object id at the moment.
          \param displayMode can be the following values:\n
          CELL_TRIANGLE: display the mesh using filled triangles.\n
          CELL_LINE: display the mesh using lines.\n
          CELL_POINT: display the mesh using points.\n
    */
    void DrawCellObjectDirect(int id, int selected, int secondSelected, int displayMode);

    //! Directly draw the object transparently.
    /*!
         \param id the name id of this object.
         \param displayMode can be the following values:\n
          CELL_TRIANGLE: display the mesh using filled triangles.\n
          CELL_LINE: display the mesh using lines.\n
          CELL_POINT: display the mesh using points.\n
         \param transparencyLvl can be the following values:\n
          LOW_TRANSPARENCY: low transparency as in closer to being a solid\n
          MID_TRANSPARENCY: medium tranparency\n
          HIGH_TRANSPARENCY: high transparency as in closer to being clear\n
         \param color can be the following values:\n
          BLUE\n
          GREEN\n
          RED\n
          PURPLE\n
     */
    void DrawCellObjectDirectTransparent(int id, int displayMode, int transparencyLvl, int color);

    //! Call ball-pivoting algorithm to reconstruct surface.
    /*!
          \param radius the parameter of ball-pivoting algorithm.
    */
    void SurfaceReconstruction(double radius);

    //! Save the infomation of object to the file.
    /*!
          \param filename the name of saved file.
    */
    void SaveCellObject(char *filename);

    //! Compute the outline of the mesh under the plane base view.
    /*!
          \param a a value in the plane function ax+by+cz+d=0.
          \param b b value in the plane function ax+by+cz+d=0.
          \param c c value in the plane function ax+by+cz+d=0.
          \param d d value in the plane function ax+by+cz+d=0.
    */
    void ComputeIntersectionPlane(double a, double b, double c, double d);

    //! Draw the outline of the mesh under the plane base view.
    void DrawIntersectionPlane();

    //Transform Object func
    void setTranslateX(double x) { translateX = x; }
    void setTranslateY(double y) { translateY = y; }
    void setTranslateZ(double z) { translateZ = z; }
    void setTranslate(double x, double y, double z) {translateX = x; translateY = y; translateZ = z;}
    void addTranslateX(double x);
    void addTranslateY(double y);
    void addTranslateZ(double z);
    void addTranslateCtrlPt1X(double x);
    void addTranslateCtrlPt1Y(double y);
    void addTranslateCtrlPt1Z(double z);
    void addTranslateCtrlPt2X(double x);
    void addTranslateCtrlPt2Y(double y);
    void addTranslateCtrlPt2Z(double z);
    void setRotateX(double x) {rotateX = x;}
    void setRotateY(double y) {rotateY = y;}
    void setRotateZ(double z) {rotateZ = z;}
    void addRotateCtrl(double a, double x, double y, double z);
    void addRotateX(double x);
    void addRotateY(double y);
    void addRotateZ(double z);
    void setScaleX(double x) {scaleX = x;}
    void setScaleY(double y) {scaleY = y;}
    void setScaleZ(double z) {scaleZ = z;}
    void setScale(double uniform) {scaleX = uniform; scaleY = uniform; scaleZ = uniform;}
    double getTranslateX() {return translateX;}
    double getTranslateY() {return translateY;}
    double getTranslateZ() {return translateZ;}
    double getRotateX() {return rotateX;}
    double getRotateY() {return rotateY;}
    double getRotateZ() {return rotateZ;}
    double getRotateCtrl() {return rotateCtrl;}
    double getRotateAxisX() {return rotateAxisX;}
    double getRotateAxisY() {return rotateAxisY;}
    double getRotateAxisZ() {return rotateAxisZ;}
    double getScaleX() {return scaleX;}
    double getScaleY() {return scaleY;}
    double getScaleZ() {return scaleZ;}
    double getMaxScale();
    double getAxisX(int i) {return axisX[i];}
    double getAxisY(int i) {return axisY[i];}
    double getAxisZ(int i) {return axisZ[i];}
    bool isSurfaceConstructed();

    //! The rotation matrix.
    double rotateMatrix[4][4];

    //! The restructured rotation matrix
    double newRotateMatrix[4][4];

    //! Compute the rotation matrix rotateMatrix[4][4].
    void AxisRotate();

    //! Print the rotation matrix rotateMatrix[4][4].
    void printRotateMatrix();

    //Edit Mesh function
    //! Under the edit mesh mode base on points, call this function to draw all points of the object.
    void drawEditPoint();

    //! Under the edit mesh mode base on points, set all elements in selectedPoint[] to be false.
    void resetSelectedPoint();

    //! Choose the one point.
    /*!
          \param index set the (index)th point to be selected.
    */
    void setPointSelect(int index);

    bool isPointSelected(int index) {return selectedPoint[index];}

    //! Choose the all the points in the frustum.
    /*!
          \param point the 8 points of the frustum which define the frustum
          \param view the view direction of the camera.
          \param up the up direction of the camera.
    */
    void setPointsSelectInArea(Vector3 point[8], Vector3 view, Vector3 up);

    //! Compute the center of all the selected points.
    /*!
          \param center return the center of all the selected points.
    */
    int selectPointsCenter(Vector3 &center);

    //! Move all the selected points.
    /*!
          \param vec move the points along the length of the vec and direction of the vec.
    */
    void moveSelectPoints(Vector3 vec);

    //! Move all the points with feather.
    /*!
          \param vec move the points along the length of the vec and direction of the vec.
          \param percent effect the erea of (1+percent) of the selected area.
    */
    void moveSelectPointsFeather(Vector3 vec, double percent);

    //! Delete all the selected points.
    void deleteSelectPoint();

    //! Under the edit mesh mode base on faces, call this function to draw all faces of the object.
    void drawEditFaces();

    //! Under the edit mesh mode base on faces, set all elements in selectedFace[] to be false.
    void resetSelectedFace();

    //! Choose the one face.
    /*!
          \param index set the (index)th face to be selected.
    */
    void setFaceSelect(int index);

    //! Choose the all the faces in the frustum.
    /*!
          \param point the 8 points of the frustum which define the frustum
          \param view the view direction of the camera.
          \param up the up direction of the camera.
    */
    void setFacesSelectInArea(Vector3 point[8], Vector3 view, Vector3 up);

    //! Compute the center of all the selected faces.
    /*!
          \param center return the center of all the selected faces.
    */
    int selectFacesCenter(Vector3 &center);

    //! Move all the selected faces.
    /*!
          \param vec move the faces along the length of the vec and direction of the vec.
    */
    void moveSelectFaces(Vector3 vec);

    //! Move all the faces with feather.
    /*!
          \param vec move the faces along the length of the vec and direction of the vec.
          \param percent effect the erea of (1+percent) of the selected area.
    */
    void moveSelectFacesFeather(Vector3 vec, double percent);

    //! Delete all the selected faces.
    void deleteSelectFace();

    //Build Object func
    //! Make the object to be a sphere.
    /*!
          \param radius radius of the sphere.
          \param hseg segmentation number cross latitude of the sphere.
          \param wseg segmentation number cross longitude of the sphere.
    */
    void buildSphere(double radius, int hseg, int wseg);

    //! Make the object to be a cylinder.
    /*!
          \param radius radius of the top circular plane.
          \param height height of the cylinder.
          \param wseg segmentation number of the top circular plane.
          \param hseg segmentation number cross the height.
    */
    void buildCylinder(double radius, double height, int wseg, int hseg);

    //! Make the object to be a cylinder
    /*!
     * \brief buildCylinder based on the control points and radii
     * \param point1 center point of the top face
     * \param radius1 radius of the top face
     * \param point2 center point of the bottom face
     * \param radius2 radius of the bottom face
     */
    void buildCylinder(double x1, double y1, double z1, double radius1, double x2, double y2, double z2, double radius2);

    //! Make the object to be a open cylinder.
    /*!
          \param radius radius of the top circular plane.
          \param height height of the cylinder.
          \param wseg segmentation number of the top circular plane.
          \param hseg segmentation number cross the height.
    */
    void buildOpenCylinder(double radius, double height, int wseg, int hseg);

    //! Make the object to be a cube.
    /*!
          \param xlength length of the edge cross x axis.
          \param ylength length of the edge cross y axis.
          \param zlength length of the edge cross z axis.
          \param xseg segmentation number of the edge cross x axis.
          \param yseg segmentation number of the edge cross y axis.
          \param zseg segmentation number of the edge cross z axis.
    */
    void buildCube(double xlength, double ylength, double zlength, int xseg, int yseg, int zseg);

    //! Make the object to be a plane.
    /*!
          \param xlength length of the edge cross x axis.
          \param zlength length of the edge cross z axis.
          \param xseg segmentation number of the edge cross x axis.
          \param zseg segmentation number of the edge cross z axis.
    */
    void buildPlane(double xlength, double zlength, int xseg, int zseg);

    //! Make the object to be a cylinder.
    /*!
          \param radius radius of the circular plane.
          \param height height of the cone.
          \param wseg segmentation number of the circular plane.
    */
    void buildCone(double height, double radius, int wseg);

    //! Make the object to be a line
    /*!
          \param zlength length of the line
    */
    void buildLine(double ylength);

    //! Make the object from other data
    /*!
          \param v_num vertice number.
          \param f_num face number
          \param ver all vertices data.
          \param nor all normals data.
          \param f all face data.
    */
    void buildObject(int v_num, int f_num, double *ver, double *nor, int *f);

    //! Return the face number of this object.
    int getFaceNum() {return face_num;}

    //! Return the vertice number of this object.
    int getVerticeNum() {return vertice_num;}

    //! Return one vertex position.
    Vector3 getVertice(int index) {return Vector3(vertice[index*3+0], vertice[index*3+1], vertice[index*3+2]);}

    //! Return one vertex normal.
    Vector3 getNormal(int index) {return Vector3(normals[index*3+0], normals[index*3+1], normals[index*3+2]);}

    //! Return one face index.
    int getFace(int index) {return face[index];}

    //! Transform position and normal of all vertices.
    void transformVertex();

    //! Transform position and normal of all vertices in csg.
    void transformCSG();

    //! Transform object to csg.
    void toCSG();

    //! Transform csg to object.
    void toObject();

    //! Set csg.
    void setCSG(CSG *csg);

    //! Get csg.
    CSG* getCSG() {return csgObject;}

    //! Get type.
    int getType() {return type;}

    double getSphereRadius() {return sphereRadius;}
    int getSphereAxisSeg() {return sphereAxisSeg;}
    int getSphereHeightSeg() {return sphereHeightSeg;}

    double getCylinderRadius() {return cylinderRadius;}
    double getCylinderHeight() {return cylinderHeight;}
    int getCylinderHeightSeg() {return cylinderHeightSeg;}
    int getCylinderAxisSeg() {return cylinderAxisSeg;}

    double getPlaneWidth() {return planeWidth;}
    double getPlaneLength() {return planeLength;}
    int getPlaneWidthSeg() {return planeWidthSeg;}
    int getPlaneLengthSeg() {return planeLengthSeg;}

    double getCubeWidth() {return cubeWidth;}
    double getCubeHeight() {return cubeHeight;}
    double getCubeDepth() {return cubeDepth;}
    int getCubeWidthSeg() {return cubeWidthSeg;}
    int getCubeHeightSeg() {return cubeHeightSeg;}
    int getCubeDepthSeg() {return cubeDepthSeg;}

    double getCtrlPt1X() {return vertice[0];}
    double getCtrlPt1Y() {return vertice[1];}
    double getCtrlPt1Z() {return vertice[2];}
    double getCtrlPt2X() {return vertice[3];}
    double getCtrlPt2Y() {return vertice[4];}
    double getCtrlPt2Z() {return vertice[5];}

private:
    //! Type of the object.
    /*!
          type can be following values:\n
          CELL_MESH: this object is a normal mesh.\n
          CELL_SPHERE: this object is a sphere.\n
          CELL_CYLINDER: this object is a cylinder.\n
          CELL_CUBIC: this object is a cubic.\n
          CELL_PLANE: this object is a plane.\n
          CELL_LINE: this object is a temporary rendering. \n
    */
    int type;

    //! Store all the vertices.
    GLdouble *vertice;

    //! Store all the transformed vertices.
    GLdouble *transformedVertices;

    //! Store all the normals.
    GLdouble *normals;

    //! Store all the transformed normals.
    GLdouble *transformedNormals;

    //! Store all the faces.
    int *face;

    //! Store all the transformed faces.
    int *transformedFaces;

    //! Flag of all the selected points at this moment.
    bool *selectedPoint;

    //! Flag of all the selected faces at this moment.
    bool *selectedFace;

    //! Flag of all the deleted points at this moment.
    bool *deletePoint;

    //! Flag of all the deleted faces at this moment.
    bool *deleteFace;

    //! Store the edges intersection with a plane.
    vector<double> intersectionPlane;

    //! The number of faces of the object.
    int face_num;

    //! The number of vertice of the object.
    int vertice_num;
    double translateX, translateY, translateZ;
    double rotateX, rotateY, rotateZ;
    double rotateCtrl, rotateAxisX, rotateAxisY, rotateAxisZ;
    double scaleX, scaleY, scaleZ;

//    //! Determine if this object can use ball-pivoting algorithm.
//    /*!
//     * \brief is_surface_constructed is set to false by default in the constructor
//     *
//     * When a CellObject calls on a build member function, e.g. buildSphere, is_surface_constructed is set to true
//     */
//    bool is_surface_constructed;

    //! Determine if this object can use ball-pivoting algorithm.
    /*!
     * \brief The is_surface_constructed enum is set to POINT_CLOUD by default in the constructor
     *
     * When a CellObject calls on a build member function, e.g. buildSphere, is_surface_constructed is set to the corresponding enum
     */
    enum IS_SURFACE_CONSTRUCTED { POINT_CLOUD, LINE_SEGMENT, TRIANGLES };
    IS_SURFACE_CONSTRUCTED is_surface_constructed;

    //! The direction of the x axis after rotation.
    double axisX[4];

    //! The direction of the y axis after rotation.
    double axisY[4];

    //! The direction of the z axis after rotation.
    double axisZ[4];

    //! If the csg of this object have been constructed.
    bool isCSG;

    //! The point of csg.
    CSG *csgObject;

    double sphereRadius;
    int sphereAxisSeg;
    int sphereHeightSeg;

    double cylinderRadius;
    double cylinderHeight;
    int cylinderHeightSeg;
    int cylinderAxisSeg;

    double planeWidth;
    double planeLength;
    int planeWidthSeg;
    int planeLengthSeg;

    double cubeWidth;
    double cubeHeight;
    double cubeDepth;
    int cubeWidthSeg;
    int cubeHeightSeg;
    int cubeDepthSeg;

    //! Name of the object.
    string name;
};

#endif // CELLOBJECT_H
